package me.zhengjie.common.utils.cypto

import cn.hutool.crypto.CryptoException
import org.apache.commons.codec.binary.Base64
import org.apache.commons.lang3.tuple.Pair
import org.slf4j.LoggerFactory
import java.io.*
import java.math.BigInteger
import java.nio.charset.StandardCharsets
import java.security.*
import java.security.interfaces.RSAPrivateKey
import java.security.interfaces.RSAPublicKey
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.RSAPrivateKeySpec
import java.security.spec.RSAPublicKeySpec
import java.security.spec.X509EncodedKeySpec
import javax.crypto.BadPaddingException
import javax.crypto.Cipher
import javax.crypto.IllegalBlockSizeException
/**
 * @program: pilecloud
 * @description:
 * RSA公钥/私钥/签名工具包
 * 字符串格式的密钥在未在特殊说明情况下都为BASE64编码格式
 * 由于非对称加密速度极其缓慢，一般文件不使用它来加密而是使用对称加密
 * 非对称加密算法可以用来对对称加密的密钥加密，这样保证密钥的安全也就保证了数据的安全
 * @author: Mr.Xiao
 * @create: 2018-07-23 18:02
 */
object RSAUtils {
    private val LOGGER = LoggerFactory.getLogger(RSAUtils::class.java)

    /**
     * 密钥位数
     */
    const val RAS_KEY_SIZE = 1024

    /**
     * 加密算法RSA
     */
    const val KEY_ALGORITHM = "RSA"

    /**
     * 填充方式
     */
    const val KEY_ALGORITHM_PADDING = "RSA/ECB/PKCS1Padding"

    /**
     * 签名算法
     */
    const val SIGNATURE_ALGORITHM = "MD5withRSA"

    /**
     * RSA最大解密密文大小
     */
    private const val MAX_DECRYPT_BLOCK = 128

    /**
     * RSA最大加密明文大小
     */
    private const val MAX_ENCRYPT_BLOCK = 128 - 11

    /**
     * 生成公钥和私钥
     *
     * @param rsaKeySize key size
     *
     * @return 公钥和私钥
     */
    fun genKeyPair(rsaKeySize: Int): Pair<RSAPublicKey, RSAPrivateKey>? {
        try {
            val keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM)
            keyPairGen.initialize(rsaKeySize)
            val keyPair = keyPairGen.generateKeyPair()
            val publicKey = keyPair.public as RSAPublicKey
            val privateKey = keyPair.private as RSAPrivateKey
            return Pair.of(publicKey, privateKey)
        } catch (e: NoSuchAlgorithmException) {
            LOGGER.error("getKeys ex ", e)
        }
        return null
    }

    /**
     * 编码密钥，便于存储
     *
     * @param key 密钥
     * @return base64后的字符串
     * @throws Exception Exception
     */
    @Throws(Exception::class)
    fun encodeBase64(key: Key): String? {
        return Base64Utils.encode(key.encoded)
    }

    /**
     * 从字符串解码私钥
     *
     * @param key 密钥
     * @return base64后的字符串
     * @throws Exception Exception
     */
    @Throws(Exception::class)
    fun decodePrivateKey(key: String): PrivateKey {
        val keyBytes = Base64Utils.decode(key)
        val pkcs8KeySpec = PKCS8EncodedKeySpec(keyBytes)
        val keyFactory = KeyFactory.getInstance(KEY_ALGORITHM)
        return keyFactory.generatePrivate(pkcs8KeySpec)
    }

    /**
     * 从字符串解码公钥
     *
     * @param publicKey 公钥
     * @return 公钥
     * @throws Exception Exception
     */
    @Throws(Exception::class)
    fun decodePublicKey(publicKey: String): PublicKey {
        val keyBytes = Base64Utils.decode(publicKey)
        val x509KeySpec = X509EncodedKeySpec(keyBytes)
        val keyFactory = KeyFactory.getInstance(KEY_ALGORITHM)
        return keyFactory.generatePublic(x509KeySpec)
    }

    /**
     * 用私钥对信息生成数字签名
     *
     * @param data       已加密数据
     * @param privateKey 私钥(BASE64编码)
     * @return 私钥
     * @throws Exception Exception
     */
    @Throws(Exception::class)
    fun sign(data: ByteArray?, privateKey: String): String? {
        val signature = Signature.getInstance(SIGNATURE_ALGORITHM)
        signature.initSign(decodePrivateKey(privateKey))
        signature.update(data)
        return Base64Utils.encode(signature.sign())
    }

    /**
     * 校验数字签名
     *
     * @param data      已加密数据
     * @param publicKey 公钥(BASE64编码)
     * @param sign      数字签名
     * @return 是否通过校验
     * @throws Exception Exception
     */
    @Throws(Exception::class)
    fun verify(data: ByteArray?, publicKey: String, sign: String): Boolean {
        val signature = Signature.getInstance(SIGNATURE_ALGORITHM)
        signature.initVerify(decodePublicKey(publicKey))
        signature.update(data)
        return signature.verify(Base64Utils.decode(sign))
    }

    /**
     * 使用模和指数生成RSA公钥
     * 注意：【此代码用了默认补位方式，为RSA/None/PKCS1Padding，
     * 不同JDK默认的补位方式可能不同，如Android默认是RSA
     * /None/NoPadding】
     *
     * @param modulus  模
     * @param exponent 指数
     * @return 公钥
     */
    fun getPublicKey(modulus: String, exponent: String): RSAPublicKey {
        return try {
            val b1 = BigInteger(modulus)
            val b2 = BigInteger(exponent)
            val keyFactory = KeyFactory.getInstance(KEY_ALGORITHM)
            val keySpec = RSAPublicKeySpec(b1, b2)
            keyFactory.generatePublic(keySpec) as RSAPublicKey
        } catch (e: Exception) {
            LOGGER.error("getPublicKey ex modulus={$modulus}, exponent={$exponent}", e)
            throw CryptoException("Get PublicKey ex", e)
        }
    }

    /**
     * 使用模和指数生成RSA私钥
     * 注意：【此代码用了默认补位方式，为RSA/None/PKCS1Padding，
     * 不同JDK默认的补位方式可能不同，如Android默认是RSA
     * /None/NoPadding】
     *
     * @param modulus  模
     * @param exponent 指数
     * @return 私钥
     */
    fun getPrivateKey(modulus: String, exponent: String): RSAPrivateKey {
        return try {
            val b1 = BigInteger(modulus)
            val b2 = BigInteger(exponent)
            val keyFactory = KeyFactory.getInstance(KEY_ALGORITHM)
            val keySpec = RSAPrivateKeySpec(b1, b2)
            keyFactory.generatePrivate(keySpec) as RSAPrivateKey
        } catch (e: Exception) {
            LOGGER.error("getPrivateKey ex modulus={$modulus}, exponent={$exponent}", e)
            throw CryptoException("Get PrivateKey ex", e)
        }
    }

    /**
     * 公钥加密
     *
     * @param data      待加密数据
     * @param publicKey 公钥
     * @return 加密后的值
     */
    fun encryptByPublicKey(data: ByteArray, publicKey: RSAPublicKey): ByteArray {
        return try {
            val cipher = Cipher.getInstance(KEY_ALGORITHM_PADDING)
            cipher.init(Cipher.ENCRYPT_MODE, publicKey)
            // 模长
            val key_len = publicKey.modulus.bitLength() / 8
            // 加密数据长度 <= 模长-11
            //如果明文长度大于模长-11则要分组加密
            doFinal(cipher, data, key_len - 11)
        } catch (e: Exception) {
            LOGGER.error("encryptByPublicKey ex", e)
            throw CryptoException("RSA encrypt ex", e)
        }
    }

    /**
     * 私钥解密
     *
     * @param data       待加密数据
     * @param privateKey 私钥
     * @return 解密后的值
     */
    fun decryptByPrivateKey(data: ByteArray, privateKey: RSAPrivateKey): ByteArray {
        return try {
            val cipher = Cipher.getInstance(KEY_ALGORITHM_PADDING)
            cipher.init(Cipher.DECRYPT_MODE, privateKey)
            //模长
            val key_len = privateKey.modulus.bitLength() / 8
            //如果密文长度大于模长则要分组解密
            doFinal(cipher, data, key_len)
        } catch (e: Exception) {
            LOGGER.error("decryptByPrivateKey ex", e)
            throw CryptoException("RSA decrypt ex", e)
        }
    }

    /**
     * 注意：RSA加密明文最大长度117字节，
     * 解密要求密文最大长度为128字节，
     * 所以在加密和解密的过程中需要分块进行。
     *
     * @param cipher 密钥
     * @param data   待处理的数据
     * @return 处理后的值
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     */
    @Throws(BadPaddingException::class, IllegalBlockSizeException::class)
    private fun doFinal(cipher: Cipher, data: ByteArray, key_len: Int): ByteArray {
        var inputLen = data.size
        var offset = 0
        var tmp: ByteArray
        val out = ByteArrayOutputStream(getTmpArrayLength(inputLen))
        while (inputLen > 0) {
            tmp = cipher.doFinal(data, offset, Math.min(key_len, inputLen))
            out.write(tmp, 0, tmp.size)
            offset += key_len
            inputLen -= key_len
        }
        return out.toByteArray()
    }

    private fun getTmpArrayLength(L: Int): Int {
        var S = MAX_DECRYPT_BLOCK
        while (S < L) S = S shl 1
        return S
    }

    /**
     * 私钥解密
     *
     * @param data       已加密数据
     * @param privateKey 私钥(BASE64编码)
     * @return 解密后的值
     * @throws Exception Exception
     */
    @Throws(Exception::class)
    fun decryptByPrivateKey(data: ByteArray, privateKey: String): ByteArray {
        val key = decodePrivateKey(privateKey)
        val cipher = Cipher.getInstance(KEY_ALGORITHM_PADDING)
        cipher.init(Cipher.DECRYPT_MODE, key)
        return doFinal(cipher, data, MAX_DECRYPT_BLOCK)
    }

    /**
     * 公钥解密
     *
     * @param data      已加密数据
     * @param publicKey 公钥(BASE64编码)
     * @return 解密后的值
     * @throws Exception Exception
     */
    @Throws(Exception::class)
    fun decryptByPublicKey(data: ByteArray, publicKey: String): ByteArray {
        val key = decodePublicKey(publicKey)
        val cipher = Cipher.getInstance(KEY_ALGORITHM_PADDING)
        cipher.init(Cipher.DECRYPT_MODE, key)
        return doFinal(cipher, data, MAX_DECRYPT_BLOCK)
    }


    /**
     * 私钥解密
     *
     * @param privateKeyText 私钥
     * @param text 待解密的文本
     * @return /
     * @throws Exception /
     */
    @Throws(java.lang.Exception::class)
    fun decryptByPrivateKey(privateKeyText: String?, text: String?): String? {
        val pkcs8EncodedKeySpec5 = PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText))
        val keyFactory = KeyFactory.getInstance("RSA")
        val privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5)
        val cipher = Cipher.getInstance("RSA")
        cipher.init(Cipher.DECRYPT_MODE, privateKey)
        val result: ByteArray =
            doLongerCipherFinal(Cipher.DECRYPT_MODE, cipher, Base64.decodeBase64(text))!!
        return String(result)
    }

    @Throws(java.lang.Exception::class)
    fun doLongerCipherFinal(opMode: Int, cipher: Cipher, source: ByteArray): ByteArray? {
        val out = ByteArrayOutputStream()
        if (opMode == Cipher.DECRYPT_MODE) {
            out.write(cipher.doFinal(source))
        } else {
            var offset = 0
            val totalSize = source.size
            while (totalSize - offset > 0) {
                val size = (cipher.getOutputSize(0) - 11).coerceAtMost(totalSize - offset)
                out.write(cipher.doFinal(source, offset, size))
                offset += size
            }
        }
        out.close()
        return out.toByteArray()
    }
    /**
     * 公钥加密
     *
     * @param data      源数据
     * @param publicKey 公钥(BASE64编码)
     * @return 加密后的值
     * @throws Exception Exception
     */
    @Throws(Exception::class)
    fun encryptByPublicKey(data: ByteArray, publicKey: String): ByteArray {
        val key = decodePublicKey(publicKey)
        // 对数据加密
        val cipher = Cipher.getInstance(KEY_ALGORITHM_PADDING)
        cipher.init(Cipher.ENCRYPT_MODE, key)
        return doFinal(cipher, data, MAX_ENCRYPT_BLOCK)
    }

    /**
     * 私钥加密
     *
     * @param data       源数据
     * @param privateKey 私钥(BASE64编码)
     * @return 加密后的值
     * @throws Exception Exception
     */
    @Throws(Exception::class)
    fun encryptByPrivateKey(data: ByteArray, privateKey: String): ByteArray {
        val key = decodePrivateKey(privateKey)
        val cipher = Cipher.getInstance(KEY_ALGORITHM_PADDING)
        cipher.init(Cipher.ENCRYPT_MODE, key)
        return doFinal(cipher, data, MAX_ENCRYPT_BLOCK)
    }

    private fun test() {
        val pair = genKeyPair(RAS_KEY_SIZE)
        //生成公钥和私钥
        val publicKey = pair!!.key
        val privateKey = pair.value
        //模
        val modulus = publicKey.modulus.toString()
        //公钥指数
        val public_exponent = publicKey.publicExponent.toString()
        //私钥指数
        val private_exponent = privateKey.privateExponent.toString()
        //明文
        var ming = "123456789".toByteArray(StandardCharsets.UTF_8)
        println("明文：" + String(ming, StandardCharsets.UTF_8))
        //使用模和指数生成公钥和私钥
        val priKey = getPrivateKey(modulus, private_exponent)
        val pubKey = getPublicKey(modulus, public_exponent)
        println("privateKey=$priKey")
        println("publicKey=$pubKey")
        //加密后的密文
        val mi = encryptByPublicKey(ming, pubKey)
        println("密文：" + String(mi, StandardCharsets.UTF_8))
        //解密后的明文
        ming = decryptByPrivateKey(mi, priKey)
        println("解密：" + String(ming, StandardCharsets.UTF_8))
    }

//    @Throws(Exception::class)
//    @JvmStatic
//    fun main(args: Array<String>) {
//        var keySize = RAS_KEY_SIZE
//        if (args.size > 0) keySize = args[0].toInt()
//        if (keySize < RAS_KEY_SIZE) keySize = RAS_KEY_SIZE
//        val pair = genKeyPair(keySize)
//        //生成公钥和私钥
//        val publicKey = pair!!.key
//        val privateKey = pair.value
//        println("key generate success!")
//        println("privateKey=" + encodeBase64(privateKey))
//        println("publicKey=" + encodeBase64(publicKey))
//
//        //明文
//        var ming = "这是一段测试文字。。。。".toByteArray(StandardCharsets.UTF_8)
//        println("明文:" + String(ming, StandardCharsets.UTF_8))
//
//        //加密后的密文
//        val mi = encryptByPublicKey(ming, publicKey)
//        println("密文:" + String(mi, StandardCharsets.UTF_8))
//        //解密后的明文
//        ming = decryptByPrivateKey(mi, privateKey)
//        println("解密:" + String(ming, StandardCharsets.UTF_8))
//    }
}